/********************************
 * @FileName:     OffsetLimitInterceptor.java
 * @Description:  OffsetLimitInterceptor.java -
 * @Copyright:    Copyright(C) 2010-2011 mbool.com
 * @Company       MBOOL-TEAM zhangjiayi LTD.
 * @author:       zhangjiayi
 * @version       V1.0 
 * @Createdate:   2016-5-9 下午9:59:52
 *
 * All rights Reserved, Designed By MBOOL-TEAM
 *
 * Modification  History:
 * Date         Author        Version        Discription
 * -----------------------------------------------------------------------------------
 * 2016-5-9       zhangjiayi       1.0         TODO
 * Why & What is modified: <修改原因描述>
 *
 ********************************/
package com.rowline.admin.module.util.data.ibatis;

import com.rowline.admin.module.util.data.dialect.Dialect;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;


/**
 * @ClassName:     OffsetLimitInterceptor
 * @Description:   为ibatis3提供基于方言(Dialect)的分页查询的插件
 * @author:        zhangjiayi
 * @date:          2016-5-9 下午9:59:52
 *
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
    RowBounds.class, ResultHandler.class }) })
public class OffsetLimitInterceptor implements Interceptor {
	 private static int mappedStatementIndex = 0;
	    private static int parameterIndex = 1;
	    private static int rowboundsIndex = 2;

	    // 设置方言
	    private Dialect dialect;
	    
	    public Object intercept(Invocation invocation) throws Throwable {
	        processIntercept(invocation.getArgs());
	        return invocation.proceed();
	    }

	    /**
	     * ***
	     * @Title:       processIntercept
	     * @Description: 拦截分页请求，使用方言将原sql转化成分页sql processIntercept
	     * @param queryArgs   
	     * @return:      void   
	     * @throws: 
	     * @date:	     2016-5-9 下午10:02:46
	     ****
	     */
	    void processIntercept(final Object[] queryArgs) {
	        MappedStatement ms = (MappedStatement) queryArgs[mappedStatementIndex];
	        Object parameter = queryArgs[parameterIndex];
	        final RowBounds rowBounds = (RowBounds) queryArgs[rowboundsIndex];
	        int offset = rowBounds.getOffset();
	        int limit = rowBounds.getLimit();

	        if (dialect.supportsLimit() && (offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT)) {
	            BoundSql boundSql = ms.getBoundSql(parameter);
	            String sql = boundSql.getSql().trim();
	            if (dialect.supportsLimitOffset()) {
	                sql = dialect.getLimitString(sql, offset, limit);
	                offset = RowBounds.NO_ROW_OFFSET;
	            } else {
	                sql = dialect.getLimitString(sql, 0, limit);
	            }
	            limit = RowBounds.NO_ROW_LIMIT;

	            queryArgs[rowboundsIndex] = new RowBounds(offset, limit);
	            BoundSql newBoundSql =
	                new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
	            // 将原有的BoundSql中的MetaParameter复制到新的BoundSql中
	            copyMetaParameters(boundSql, newBoundSql);
	            MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
	            queryArgs[mappedStatementIndex] = newMs;
	        }
	    }
	        
	    /**
	     * ***
	     * @Title:       copyMetaParameters
	     * @Description: 复制BoundSql的MetaParameter
	     * @param lhs
	     * @param rhs   
	     * @return:      void   
	     * @throws: 
	     * @date:	     2016-5-9 下午10:03:43
	     ****
	     */
	    private void copyMetaParameters(BoundSql lhs, BoundSql rhs) {
	        for (ParameterMapping map : lhs.getParameterMappings()) {
	            String key = map.getProperty();
	            Object value = lhs.getAdditionalParameter(key);
	            if (null != value) {
	                rhs.setAdditionalParameter(key, value);
	            }
	        }
	    }

	    /**
	     * ***
	     * @Title:       copyFromMappedStatement
	     * @Description: 获取MappedStatement
	     * @param ms
	     * @param newSqlSource
	     * @return   
	     * @return:      MappedStatement   
	     * @throws: 
	     * @date:	     2016-5-9 下午10:03:54
	     ****
	     */
	    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
	        Builder builder =
	            new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

	        builder.resource(ms.getResource());
	        builder.fetchSize(ms.getFetchSize());
	        builder.statementType(ms.getStatementType());
	        builder.keyGenerator(ms.getKeyGenerator());

	        String[] kps = ms.getKeyProperties();
	        if(null != kps && kps.length > 0)
	        builder.keyProperty(kps[0]);

	        // setStatementTimeout()
	        builder.timeout(ms.getTimeout());

	        // setStatementResultMap()
	        builder.parameterMap(ms.getParameterMap());

	        // setStatementResultMap()
	        builder.resultMaps(ms.getResultMaps());
	        builder.resultSetType(ms.getResultSetType());

	        // setStatementCache()
	        builder.cache(ms.getCache());
	        builder.flushCacheRequired(ms.isFlushCacheRequired());
	        builder.useCache(ms.isUseCache());

	        return builder.build();
	    }

	    public Object plugin(Object target) {
	        return Plugin.wrap(target, this);
	    }
	    /**
	     * *
	     * Title:       {overriding}- setProperties
	     * Description:  设置方言
	     * @param properties
	     * @see org.apache.ibatis.plugin.Interceptor#setProperties(Properties)
	     * Date:	     2016-5-9 下午10:27:35
	     **
	     */
	    public void setProperties(Properties properties) {

	    	
	    	String dialectClass = properties.getProperty("dialectClass","com.deppon.dop.platform.module.common.data.dialect.MysqlDialect");
	        
	    	try {
	            dialect = (Dialect) Class.forName(dialectClass).newInstance();
	        } catch (Exception e) {
	            throw new RuntimeException("cannot create dialect instance by dialectClass:" + dialectClass, e);
	        }
	    }
	    
	    /**
	     * 
	     * @ClassName:     BoundSqlSqlSource
	     * @Description:   设置分页boundsql
	     * @author:        zhangjiayi
	     * @date:          2016-5-9 下午10:27:56
	     *
	     */
	    public static class BoundSqlSqlSource implements SqlSource {
	        private BoundSql boundSql;

	        public BoundSqlSqlSource(BoundSql boundSql) {
	            this.boundSql = boundSql;
	        }

	        public BoundSql getBoundSql(Object parameterObject) {
	            return boundSql;
	        }
	    }
}
